package com.yaolong.api.modules.gen.genengines;

import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.engine.AbstractTemplateEngine;
import com.baomidou.mybatisplus.generator.util.FileUtils;
import com.yaolong.api.modules.gen.config.CustomConfigs;
import com.yaolong.api.modules.gen.config.CustomOutputTemplateResultConfig;
import com.yaolong.api.modules.gen.constants.FileGroupType;
import com.yaolong.api.modules.gen.constants.GenConstants;
import com.yaolong.api.modules.gen.utils.FileUtil;
import com.yaolong.api.modules.gen.utils.TemplateUtil;
import com.yaolong.api.modules.gen.vo.GenListConfigEntity;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author yaolong
 * @version V1.0.0
 * @program AgileFastFK3.0
 * @description 自定义模板引擎抽象类
 * @create 2022-12-31 14:44
 **/
public abstract class CustomAbstractTemplateEngine extends AbstractTemplateEngine {

    @Override
    protected void outputCustomFile(@NotNull Map<String, String> customFile, @NotNull TableInfo tableInfo, @NotNull Map<String, Object> objectMap) {
        String entityName = tableInfo.getEntityName();
        String otherPath = this.getPathInfo(OutputFile.other);
        CustomConfigs customConfigs = (CustomConfigs) objectMap.get(GenConstants.CUSTOM_CONFIG_FIELD);
        ConfigBuilder config = (ConfigBuilder) objectMap.get(GenConstants.CONFIG_FIELD);
        customFile.forEach((key, value) -> {
            String templatePath = value;
            String fileName = String.format(otherPath + File.separator + entityName + File.separator + "%s", key);
            //如果是数据库模版 数据库查出来的模板都是
            if (value.startsWith(GenConstants.TEMPLATE_DB_TYPE + GenConstants.TEMPLATE_TYPE_SEPARATOR) && customConfigs.getCustomTemplateConfig().getEnableDbTemplate()) {
                String[] split = value.split(GenConstants.TEMPLATE_TYPE_SEPARATOR);
                String templateName = split[1];
                String genDir = getGenDir(entityName, config.getPackageConfig().getModuleName(), split[2]);
                //设置template
                templatePath = templateName;
                String fileGenType = split[3];
                if (split.length == 5){
                    //如果存在模板文件路径就把路径作为templatePath
                    templatePath = templateFilePath(split[4]);
                }

                //如果是vue模板
                if (fileGenType.equals(GenConstants.FILE_GEN_TYPE_VUE)) {
                    fileName = getFilePath(entityName, customConfigs.getCustomFileConfig().getVueParentPath(), key, genDir);
                    //这里将文件名开头变为大写字母，前端适配一下
                    List<String> capFirstArray = Arrays.asList("VUEApiModel,VUEViewsTree,VUEViewsModal,VUEViewsDrawer".split(","));
                    if (capFirstArray.contains(templateName)) {
                        fileName = getFilePath(entityName, customConfigs.getCustomFileConfig().getVueParentPath(), TemplateUtil.capitalFirst(new File(fileName).getName()), genDir);
                    }
                } else if (fileGenType.equals(GenConstants.FILE_GEN_TYPE_JAVA)) {
                    //TODO Java模板 VO / DTO / VO 等等文件路径生成处理方式
                    fileName = getFilePath(entityName, customConfigs.getCustomFileConfig().getJavaParentPath(), key, genDir);
                } else if (fileGenType.equals(GenConstants.FILE_GEN_TYPE_SQL)) {
                    //TODO SQL模板 Oracle\mysql等Sql文件模板 等等文件路径生成处理方式
                    fileName = getFilePath(entityName, customConfigs.getCustomFileConfig().getSqlParentPath(), key, genDir);
                } else {
                    // TODO 其他 模板文件路径生成处理方式
                    fileName = getFilePath(entityName, customConfigs.getCustomFileConfig().getOtherParentPath(), key, genDir);
                }
            }
            this.outputFileBefore(new File(fileName), objectMap, templatePath, this.getConfigBuilder().getInjectionConfig().isFileOverride());
        });
    }

    /**
     * <p>
     * getFilePath
     * </p>
     *
     * @param entityName
     * @param parentPath 生成的父路径
     * @param key
     * @param genDir     生成的文件目录
     * @return java.lang.String
     * @version 1.0.0 <br>
     * @date 2023/1/9 12:35 <br>
     * @author yaolonga <br>
     */
    private String getFilePath(String entityName, String parentPath, String key, String genDir) {
        String customPath = parentPath + genDir;
        return String.format(customPath + File.separator + key, TemplateUtil.lowercaseFirst(entityName));
    }

    @NotNull
    private String getGenDir(String entityName, String moduleName, String template) {
        //模板地址有可能存在模板语法${}这里需要处理一下，把变量填充上
        HashMap<String, String> map = new HashMap<>();
        map.put("module", moduleName);
        map.put("entity", TemplateUtil.lowercaseFirst(entityName));
        return TemplateUtil.parse(template, map);
    }


    @Override
    protected void outputEntity(@NotNull TableInfo tableInfo, @NotNull Map<String, Object> objectMap) {
        String entityName = tableInfo.getEntityName();
        String entityPath = this.getPathInfo(OutputFile.entity);
        if (StringUtils.isNotBlank(entityName) && StringUtils.isNotBlank(entityPath)) {
            this.getTemplateFilePath((template) -> {
                return template.getEntity(this.getConfigBuilder().getGlobalConfig().isKotlin());
            }).ifPresent((entity) -> {
                String entityFile = String.format(entityPath + File.separator + "%s" + this.suffixJavaOrKt(), entityName);
                this.outputFileBefore(new File(entityFile), objectMap, entity, this.getConfigBuilder().getStrategyConfig().entity().isFileOverride());
            });
        }
    }


    protected void outputFileBefore(@NotNull File file, @NotNull Map<String, Object> objectMap, @NotNull String templatePath, boolean fileOverride) {
        //输出前做处理
        this.outputFile(file, objectMap, templatePath, fileOverride);
    }

    /**
     * 其他文件的注入策略，可以在此进行遍历的自定义
     */
    private void injectCustomMap(@NotNull Map<String, Object> objectMap, CustomConfigs customConfigs) {
        //TODO 先暂时在这里制定其他文件生成的策略，后面源码研究透彻再换其他方式实现 （此种方式不够优雅）
        objectMap.put("tsModelName", TemplateUtil.lowercaseFirst((String) objectMap.get("entity")));
        // 自定义表信息
        objectMap.put("customTableInfo", customConfigs.getCustomFileConfig().getGenTableConfigEntity());
        // 自定义前端页面配置
        objectMap.put("customPageConfig", customConfigs.getCustomFileConfig().getGenTableConfigEntity().getListConfigEntity());
        objectMap.put("version", customConfigs.getCustomGlobalConfig().getVersion());
        objectMap.put("moduleNameZh", customConfigs.getCustomGlobalConfig().getModuleNameZh());
        // 开始生成sql文件的menuId
        objectMap.put("mysqlStartMenuId", customConfigs.getCustomOutputTemplateResultConfig().getCurrentMysqlMenuId() + 1 );
        objectMap.put("mysqlStartMetaMenuId", customConfigs.getCustomOutputTemplateResultConfig().getCurrentMysqlMenuMetaId() + 1);

        objectMap.put("oracleStartMenuId", customConfigs.getCustomOutputTemplateResultConfig().getCurrentOracleMenuId() + 1);
        objectMap.put("oracleStartMetaMenuId", customConfigs.getCustomOutputTemplateResultConfig().getCurrentOracleMenuMetaId() + 1);
    }

//    @Override
//    public @NotNull Map<String, Object> getObjectMap(@NotNull ConfigBuilder config, @NotNull TableInfo tableInfo) {
//        Map<String, Object> objectMap = super.getObjectMap(config, tableInfo);
//        objectMap.put("tsModelName", TemplateUtil.lowercaseFirst((String) objectMap.get("entity")));
//        return objectMap;
//    }

    @Override
    protected void outputFile(@NotNull File file, @NotNull Map<String, Object> objectMap, @NotNull String templatePath, boolean fileOverride) {

        CustomConfigs customConfigs = (CustomConfigs) objectMap.get(GenConstants.CUSTOM_CONFIG_FIELD);
        //注入自定义策略map变量
        injectCustomMap(objectMap, customConfigs);


        String outType = customConfigs.getCustomGlobalConfig().getOutputType();

        if (outType.equals(GenConstants.OUTPUT_TYPE_FILE)) {
            //生成模式
            String genMode = customConfigs.getCustomGlobalConfig().getGenMode();
            //项目内生成
            if (genMode.equals(GenConstants.GEN_MODE_INNER)) {
                if (this.isCreate(file, fileOverride)) {
                    try {
                        boolean exist = file.exists();
                        if (!exist) {
                            File parentFile = file.getParentFile();
                            FileUtils.forceMkdir(parentFile);
                        }
                        this.writer(objectMap, templatePath, file);
                    } catch (Exception var7) {
                        throw new RuntimeException(var7);
                    }
                }
                // TODO 给系统表执行sql语句
            } else if (genMode.equals(GenConstants.GEN_MODE_ZIP)) {
                downloadFile(objectMap, templatePath, file);
            }

        } else if (outType.equals(GenConstants.OUTPUT_TYPE_STRING)) {
            try {
                this.writer2String(objectMap, templatePath, file);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        //当前生成的文件类型
        String fileType = file.getAbsolutePath().substring(file.getAbsolutePath().lastIndexOf("."));
        //SQL文件生成时的专门处理
        if (FileGroupType.SQL.contains(fileType)) {
            CustomOutputTemplateResultConfig resultConfig = customConfigs.getCustomOutputTemplateResultConfig();
            //处理生成SQL时菜单id自增
            if ("Oracle.sql".equals(file.getName())) {
                handlerIncreaseCurrentMenuId(resultConfig, customConfigs, GenConstants.DB_TYPE_ORACLE);
            } else if ("Mysql.sql".equals(file.getName())) {
                handlerIncreaseCurrentMenuId(resultConfig, customConfigs, GenConstants.DB_TYPE_MYSQL);
            }
            //TODO 其他数据库SQL
        }

        System.out.println(String.format("生成文件：%s", file.getAbsolutePath()));
    }

    /**
     * <p>
     * zip文件下载
     * </p>
     *
     * @param objectMap
     * @param templatePath
     * @param file
     * @return void
     * @version 1.0.0 <br>
     * @date 2022/12/31 22:59 <br>
     * @author yaolonga <br>
     */
    private void downloadFile(Map<String, Object> objectMap, String templatePath, File file) {
        try {
            CustomConfigs customConfigs = (CustomConfigs) objectMap.get(GenConstants.CUSTOM_CONFIG_FIELD);
            ConfigBuilder configBuilder = (ConfigBuilder) objectMap.get(GenConstants.CONFIG_FIELD);
            CustomOutputTemplateResultConfig resultConfig = customConfigs.getCustomOutputTemplateResultConfig();
            this.writer2String(objectMap, templatePath, file);
            //如果输出完毕就全部下载
            if (resultConfig.isOutputDone()) {
                //TODO 批量文件
                String frontEndOutputDir = new File(customConfigs.getCustomFileConfig().getVueParentPath()).getAbsolutePath();
                String sqlEndOutputDir = new File(customConfigs.getCustomFileConfig().getSqlParentPath()).getAbsolutePath();
                String otherOutputDir = new File(customConfigs.getCustomFileConfig().getOtherParentPath()).getAbsolutePath();
                String afterEndOutputDir = new File(customConfigs.getCustomFileConfig().getJavaParentPath()).getAbsolutePath();

                //生成的代码
                Map<String, String> otherCode = resultConfig.getOtherCode();
                Map<String, String> sqlCode = resultConfig.getSqlCode();
                Map<String, String> afterEndCode = resultConfig.getAfterEndCode();
                Map<String, String> frontEndCode = resultConfig.getFrontEndCode();
                String zipFileName = customConfigs.getCustomGlobalConfig().getZipName();
                //临时文件存放目录
                String tempPath = System.getProperty("user.dir") + File.separator + zipFileName;
                //zip文件地址
                String zipFilePath = tempPath + ".zip";

                Map<String, String> codes = new HashMap<>();
                formatDownloadFilePath(otherOutputDir, otherCode, codes, "/other", tempPath);
                formatDownloadFilePath(frontEndOutputDir, frontEndCode, codes, "/web", tempPath);
                formatDownloadFilePath(afterEndOutputDir, afterEndCode, codes, "/java", tempPath);
                formatDownloadFilePath(sqlEndOutputDir, sqlCode, codes, "/sql", tempPath);

                //将codes输出为文件
                codes.forEach(FileUtil::writerStr2File);
                //将输出的文件打包成zip并响应给前端
                FileUtil.packageZip(tempPath, System.getProperty("user.dir"), zipFileName);

                FileUtil.downloadFile(zipFilePath);
                //删除临时文件
                FileUtil.delFolder(tempPath);
                Files.deleteIfExists(Paths.get(zipFilePath));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    private void formatDownloadFilePath(String outputDir, Map<String, String> code, Map<String, String> codes, String suffixPath, String tempFilePath) {
        //如果有文件后缀直接去掉
        if (outputDir.endsWith(File.separator)) {
            outputDir = outputDir.substring(0, outputDir.length() - 1);
        }
        if (ObjectUtils.isNotEmpty(code)) {
            String finalOutputDir = outputDir;
            code.forEach((filePath, fileStr) -> {
                //替换文件路径为项目根目录
                String rootDir = tempFilePath + File.separator + suffixPath;
                String replace = new File(filePath.replace(finalOutputDir, rootDir)).getAbsolutePath();
                codes.put(replace, fileStr);
            });
        }
    }

    public abstract String writer2String(Map<String, Object> objectMap, String templatePath, File file) throws IOException;


    protected void setResult(@NotNull Map<String, Object> objectMap, String filePath, String templateStr) {
        //模板生成结果设置
        if (objectMap.containsKey(GenConstants.CUSTOM_CONFIG_FIELD)) {
            CustomConfigs customConfigs = (CustomConfigs) objectMap.get(GenConstants.CUSTOM_CONFIG_FIELD);
            CustomOutputTemplateResultConfig resultConfig = customConfigs.getCustomOutputTemplateResultConfig();
            if (null != resultConfig) {
                String fileType = filePath.substring(filePath.lastIndexOf("."));
                if (FileGroupType.JAVA_AFTER_END.contains(fileType)) {
                    setAfterEndResult(filePath, templateStr, resultConfig);
                } else if (FileGroupType.SQL.contains(fileType)) {
                    setSqlResult(filePath, templateStr, resultConfig);
                } else if (FileGroupType.VUE_FRONT_END.contains(fileType)) {
                    setFrontEndResult(filePath, templateStr, resultConfig);
                } else {
                    setOtherResult(filePath, templateStr, resultConfig);
                }

                //生成完毕后新增输出文件数
                resultConfig.increaseFileCount();
            }
        }
    }

    protected void handlerIncreaseCurrentMenuId(CustomOutputTemplateResultConfig resultConfig, CustomConfigs customConfigs, String dbType) {
        //根据页面配置信息的按钮是是否展示来判断生成了多少条菜单
        //页面配置信息
        GenListConfigEntity pageConfigInfo = customConfigs.getCustomFileConfig().getGenTableConfigEntity().getListConfigEntity();
        if (pageConfigInfo.getExistSearchField()) {
            //当前表生成完毕后菜单id同步自增
            resultConfig.increaseCurrentMenuId(dbType);
        }

        if (pageConfigInfo.getIsBatch()) {
            //当前表生成完毕后菜单id同步自增
            resultConfig.increaseCurrentMenuId(dbType);
        }

        if (pageConfigInfo.getShowAddBtn()) {
            resultConfig.increaseCurrentMenuId(dbType);
        }

        if (pageConfigInfo.getShowUpdateBtn()) {
            resultConfig.increaseCurrentMenuId(dbType);
        }

        if (pageConfigInfo.getShowDelBtn()) {
            resultConfig.increaseCurrentMenuId(dbType);
        }

        if (pageConfigInfo.getShowExportBtn()) {
            resultConfig.increaseCurrentMenuId(dbType);
        }

        if (pageConfigInfo.getShowImportBtn()) {
            resultConfig.increaseCurrentMenuId(dbType);
        }

    }


    private void setFrontEndResult(String fileName, String templateString, CustomOutputTemplateResultConfig resultConfig) {
        Map<String, String> resMap;
        //如果不存在结果，就new一个
        if (null == resultConfig.getFrontEndCode()) {
            resMap = new HashMap<>();
            resultConfig.setFrontEndCode(resMap);
        } else {
            resMap = resultConfig.getFrontEndCode();
        }
        resMap.put(fileName, templateString);
    }

    private void setSqlResult(String fileName, String templateString, CustomOutputTemplateResultConfig resultConfig) {
        Map<String, String> resMap;
        //如果不存在结果，就new一个
        if (null == resultConfig.getSqlCode()) {
            resMap = new HashMap<>();
            resultConfig.setSqlCode(resMap);
        } else {
            resMap = resultConfig.getSqlCode();
        }
        resMap.put(fileName, templateString);
    }

    private void setAfterEndResult(String fileName, String templateString, CustomOutputTemplateResultConfig resultConfig) {
        Map<String, String> resMap;
        //如果不存在结果，就new一个
        if (null == resultConfig.getAfterEndCode()) {
            resMap = new HashMap<>();
            resultConfig.setAfterEndCode(resMap);
        } else {
            resMap = resultConfig.getAfterEndCode();
        }
        resMap.put(fileName, templateString);
    }

    private void setOtherResult(String fileName, String templateString, CustomOutputTemplateResultConfig resultConfig) {
        Map<String, String> resMap;
        //如果不存在结果，就new一个
        if (null == resultConfig.getOtherCode()) {
            resMap = new HashMap<>();
            resultConfig.setOtherCode(resMap);
        } else {
            resMap = resultConfig.getOtherCode();
        }
        resMap.put(fileName, templateString);
    }


}
